/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.binding.guicomponents;

import java.util.*;
import javax.swing.*;
import javax.swing.text.*;
import ch.trackedbean.binding.beanSpec.*;
import ch.trackedbean.binding.guicomponents.action.*;
import ch.trackedbean.binding.guicomponents.renderers.*;
import ch.trackedbean.binding.mapping.*;
import ch.trackedbean.loaders.lists.*;

/**
 * Datapanel with a mapping and a selection based popup.
 * 
 * @author M. Hautle
 * @param <T> The model type
 */
public class DataPanel<T> extends JPanel {
	/** The mapping */
	protected final MappingGroup<T> mapping;

	/** The popup manager. */
	protected LocalPopupManager popupManager = new LocalPopupManager();

	/**
	 * Default constructor.
	 * 
	 * @param readOnly True if the binding should be read only.
	 */
	public DataPanel(boolean readOnly) {
		mapping = new MappingGroup<T>(readOnly);
		super.setComponentPopupMenu(new JPopupMenu());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public JPopupMenu getComponentPopupMenu() {
		final T model = getModel();
		if (model == null)
			return null;
		final JPopupMenu menu = super.getComponentPopupMenu();
		popupManager.populate(menu, Collections.singletonList(model));
		return menu.getComponentCount() > 0 ? menu : null;
	}

	/**
	 * This panel has it's own popup, so this method returns <code>false</code>
	 * 
	 * @return false
	 */
	@Override
	public boolean getInheritsPopupMenu() {
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setComponentPopupMenu(JPopupMenu popup) {
		throw new UnsupportedOperationException("This method is not supported!");
	}

	/**
	 * Adds the given label to the panel and registers it on the given property.
	 * 
	 * @param path The path to the property to map
	 * @param label The label to add
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JLabel label) {
		add(label);
		return mapping.createBinding(path, label);
	}

	/**
	 * Adds the given label to the panel and registers it on the given property.
	 * 
	 * @param path The path to the property to map
	 * @param label The label to add
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JLabel label, Object constraints) {
		add(label, constraints);
		return mapping.createBinding(path, label);
	}

	/**
	 * Adds the given textcomponent to the panel and registers it on the given property.
	 * 
	 * @param path The path to the property to map
	 * @param text The textcomponent to add
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JTextComponent text) {
		add(text);
		return mapping.createBinding(path, text);
	}

	/**
	 * Adds the given textcomponent to the panel and registers it on the given property.
	 * 
	 * @param path The path to the property to map
	 * @param text The textcomponent to add
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JTextComponent text, Object constraints) {
		add(text, constraints);
		return mapping.createBinding(path, text);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * Consider using {@link #add(String, JComboBox, Class)}.
	 * 
	 * @param path The path to the property to map
	 * @param combobox The combobox to add
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JComboBox combobox) {
		add(combobox);
		return mapping.createBinding(path, combobox);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * Consider using {@link #add(String, JComboBox, Class,Object)}.
	 * 
	 * @param path The path to the property to map
	 * @param combobox The combobox to add
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JComboBox combobox, Object constraints) {
		add(combobox, constraints);
		return mapping.createBinding(path, combobox);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class)}.
	 * 
	 * @param path The path to the property to map
	 * @param combobox The combobox to add
	 * @param type The content type of the combobox list
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JComboBox combobox, Class<?> type) {
		add(combobox);
		return mapping.createBinding(path, combobox, type, null, null);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class)}.
	 * 
	 * @param path The path to the property to map
	 * @param combobox The combobox to add
	 * @param type The content type of the combobox list
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JComboBox combobox, Class<?> type, Object constraints) {
		add(combobox, constraints);
		return mapping.createBinding(path, combobox, type, null, null);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String)}.
	 * 
	 * @param path The path to the property to map
	 * @param combobox The combobox to add
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JComboBox combobox, Class<?> type, String subType) {
		add(combobox);
		return mapping.createBinding(path, combobox, type, subType, null);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String)}.
	 * 
	 * @param path The path to the property to map
	 * @param combobox The combobox to add
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JComboBox combobox, Class<?> type, String subType, Object constraints) {
		add(combobox, constraints);
		return mapping.createBinding(path, combobox, type, subType, null);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String, Map)}.
	 * 
	 * @param path The path to the property to map
	 * @param combobox The combobox to add
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @param settings Additional settings for the combobox content load (may be null)
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JComboBox combobox, Class<?> type, String subType, Map<Object, Object> settings) {
		add(combobox);
		return mapping.createBinding(path, combobox, type, subType, settings);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String, Map)}.
	 * 
	 * @param path The path to the property to map
	 * @param combobox The combobox to add
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @param settings Additional settings for the combobox content load (may be null)
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JComboBox combobox, Class<?> type, String subType, Map<Object, Object> settings, Object constraints) {
		add(combobox, constraints);
		return mapping.createBinding(path, combobox, type, subType, settings);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param combobox The {@link JComboBox} to link
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 * @throws IllegalStateException if the group is already bound
	 */
	public BindingInfos add(final String path, final String el, final JComboBox combobox, Object constraints) {
		combobox.setRenderer(new SimpleELListRenderer(el, ""));
		add(combobox, constraints);
		return mapping.createBinding(path, combobox);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class)}.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param combobox The {@link JComboBox} to link
	 * @param type The content type of the combobox list
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 * @see ListManager#getList(Class, String, Map)
	 * @see ListManager#registerLoader(Class, String, IListLoader)
	 * @throws IllegalStateException if the group is already bound
	 */
	public BindingInfos add(final String path, final String el, final JComboBox combobox, Class<?> type, Object constraints) {
		combobox.setRenderer(new SimpleELListRenderer(el, ""));
		add(combobox, constraints);
		return mapping.createBinding(path, combobox, type, null, null);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String)}.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param combobox The {@link JComboBox} to link
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 * @see ListManager#getList(Class, String, Map)
	 * @see ListManager#registerLoader(Class, String, IListLoader)
	 * @throws IllegalStateException if the group is already bound
	 */
	public BindingInfos add(final String path, final String el, final JComboBox combobox, Class<?> type, String subType, Object constraints) {
		combobox.setRenderer(new SimpleELListRenderer(el, ""));
		add(combobox, constraints);
		return mapping.createBinding(path, combobox, type, subType, null);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String, Map)}.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param combobox The {@link JComboBox} to link
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @param settings Additional settings for the combobox content load (may be null)
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 * @see ListManager#getList(Class, String, Map)
	 * @see ListManager#registerLoader(Class, String, IListLoader)
	 * @throws IllegalStateException if the group is already bound
	 */
	public BindingInfos add(final String path, final String el, final JComboBox combobox, Class<?> type, String subType, Map<Object, Object> settings,
			Object constraints) {
		combobox.setRenderer(new SimpleELListRenderer(el, ""));
		add(combobox, constraints);
		return mapping.createBinding(path, combobox, type, subType, settings);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param combobox The {@link JComboBox} to link
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 * @throws IllegalStateException if the group is already bound
	 */
	public BindingInfos add(final String path, final String el, final JComboBox combobox) {
		combobox.setRenderer(new SimpleELListRenderer(el, ""));
		add(combobox);
		return mapping.createBinding(path, combobox);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class)}.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param combobox The {@link JComboBox} to link
	 * @param type The content type of the combobox list
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 * @see ListManager#getList(Class, String, Map)
	 * @see ListManager#registerLoader(Class, String, IListLoader)
	 * @throws IllegalStateException if the group is already bound
	 */
	public BindingInfos add(final String path, final String el, final JComboBox combobox, Class<?> type) {
		combobox.setRenderer(new SimpleELListRenderer(el, ""));
		add(combobox);
		return mapping.createBinding(path, combobox, type, null, null);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String)}.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param combobox The {@link JComboBox} to link
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 * @see ListManager#getList(Class, String, Map)
	 * @see ListManager#registerLoader(Class, String, IListLoader)
	 * @throws IllegalStateException if the group is already bound
	 */
	public BindingInfos add(final String path, final String el, final JComboBox combobox, Class<?> type, String subType) {
		combobox.setRenderer(new SimpleELListRenderer(el, ""));
		add(combobox);
		return mapping.createBinding(path, combobox, type, subType, null);
	}

	/**
	 * Adds the given combobox to the panel and registers it on the given property.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String, Map)}.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param combobox The {@link JComboBox} to link
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @param settings Additional settings for the combobox content load (may be null)
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 * @see ListManager#getList(Class, String, Map)
	 * @see ListManager#registerLoader(Class, String, IListLoader)
	 * @throws IllegalStateException if the group is already bound
	 */
	public BindingInfos add(final String path, final String el, final JComboBox combobox, Class<?> type, String subType, Map<Object, Object> settings) {
		combobox.setRenderer(new SimpleELListRenderer(el, ""));
		add(combobox);
		return mapping.createBinding(path, combobox, type, subType, settings);
	}

	/**
	 * Adds the given list to the panel and registers it on the given property.
	 * 
	 * @param path The path to the property to map
	 * @param list The list to add
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JList list) {
		add(list);
		return mapping.createBinding(path, list);
	}

	/**
	 * Adds the given list to the panel and registers it on the given property.
	 * 
	 * @param path The path to the property to map
	 * @param list The list to add
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, JList list, Object constraints) {
		add(list, constraints);
		return mapping.createBinding(path, list);
	}

	/**
	 * Adds the given list to the panel and registers it on the given property.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed list.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param list The list to add
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, String el, JList list) {
		list.setCellRenderer(new SimpleELListRenderer(el, ""));
		add(list);
		return mapping.createBinding(path, list);
	}

	/**
	 * Adds the given list to the panel and registers it on the given property.<br>
	 * The passed el string is used to extract the display values out of the provided list objects.<br>
	 * This call sets simply an {@link SimpleELListRenderer} on the passed list.
	 * 
	 * @param path The path to the property to map
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @param list The list to add
	 * @param constraints The layout constraints
	 * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
	 */
	public BindingInfos add(String path, String el, JList list, Object constraints) {
		list.setCellRenderer(new SimpleELListRenderer(el, ""));
		add(list, constraints);
		return mapping.createBinding(path, list);
	}

	/**
	 * @param validator The validator to add
	 * @see ch.trackedbean.binding.mapping.MappingGroup#addGroupValidator(ch.trackedbean.binding.mapping.IGroupValidator)
	 */
	public void addGroupValidator(IGroupValidator<T> validator) {
		mapping.addGroupValidator(validator);
	}

	/**
	 *Removes the given validator.
	 * 
	 * @param validator The validator to remove
	 * @see ch.trackedbean.binding.mapping.MappingGroup#removeGroupValidator(ch.trackedbean.binding.mapping.IGroupValidator)
	 */
	public void removeGroupValidator(IGroupValidator<T> validator) {
		mapping.removeGroupValidator(validator);
	}

	/**
	 * Adds a the given action to the popup.
	 * 
	 * @param action The action
	 */
	public void addLocalAction(ISelectionAction<T> action) {
		popupManager.addLocalAction(action);
	}

	/**
	 * Adds a the given action to the popup at the given location.
	 * 
	 * @param path The location where to add the action
	 * @param action The action
	 */
	public void addLocalAction(String path, ISelectionAction<T> action) {
		popupManager.addLocalAction(path, action);
	}

	/**
	 * Removes the given local action.
	 * 
	 * @param action The action to remove
	 * @return True if the action was removed
	 * @see ch.trackedbean.binding.guicomponents.action.LocalPopupManager#removeLocalAction(ch.trackedbean.binding.guicomponents.action.ISelectionAction)
	 */
	public boolean removeLocalAction(ISelectionAction action) {
		return popupManager.removeLocalAction(action);
	}

	/**
	 * Creates a binding between the status of the source property and the element.
	 * 
	 * @param <E> The type of the element
	 * @param srcProperty The source property (where the status is taken from)
	 * @param element The target element
	 * @return The passed element
	 * @see ch.trackedbean.binding.mapping.MappingGroup#createStatusDependentBinding(java.lang.String,
	 *      ch.trackedbean.binding.mapping.IStatusDependent)
	 */
	public <E extends IStatusDependent> E bindStatusDependentElement(String srcProperty, E element) {
		return mapping.createStatusDependentBinding(srcProperty, element);
	}

	/**
	 * Binds the given {@link IDependentElement} to the given property.
	 * 
	 * @param <E> The type of the element
	 * @param property The property to bind
	 * @param element The action to bind
	 * @return The passed element
	 * @see ch.trackedbean.binding.mapping.MappingGroup#createDependentElementBinding(java.lang.String,
	 *      ch.trackedbean.binding.mapping.IDependentElement)
	 */
	public <E extends IDependentElement> E bindDependentElement(String property, E element) {
		return mapping.createDependentElementBinding(property, element);
	}

	/**
	 * @return Returns the popupManager.
	 */
	public LocalPopupManager getPopupManager() {
		return popupManager;
	}

	/**
	 * Returns the model.
	 * 
	 * @return The model
	 * @see ch.trackedbean.binding.mapping.MappingGroup#getModel()
	 */
	public T getModel() {
		return mapping.getModel();
	}

	/**
	 * Sets the given model.<br>
	 * Property validation will be enabled.
	 * 
	 * @param model The model to set
	 */
	public void setModel(T model) {
		setModel(model, true);
	}

	/**
	 * Sets the given model.
	 * 
	 * @param model The model to set
	 * @param validate True if the property validation should be activated
	 * @see ch.trackedbean.binding.mapping.MappingGroup#setModel(java.lang.Object, boolean)
	 */
	public void setModel(T model, boolean validate) {
		mapping.setModel(model, validate);
		if (validate)
			mapping.validateAllProperties();
	}

	/**
	 * Removes the current model.
	 * 
	 * @see ch.trackedbean.binding.mapping.MappingGroup#removeModel()
	 */
	public void removeModel() {
		mapping.removeModel();
	}

	/**
	 * Returns the model status.
	 * 
	 * @return The model status
	 * @see ch.trackedbean.binding.mapping.MappingGroup#getModelStatus()
	 */
	public IBeanStatus getModelStatus() {
		return mapping.getModelStatus();
	}

	/**
	 * Returns the mapping from the panel.
	 * 
	 * @return Returns the mapping.
	 */
	public MappingGroup<T> getMapping() {
		return mapping;
	}
}
